package com.hqd.ch03.v38.web.servlet.mvc.method.annotation;

import com.hqd.ch03.v38.aware.BeanFactoryAware;
import com.hqd.ch03.v38.aware.InitializingBean;
import com.hqd.ch03.v38.core.DefaultParameterNameDiscoverer;
import com.hqd.ch03.v38.core.ParameterNameDiscoverer;
import com.hqd.ch03.v38.factory.BeanFactory;
import com.hqd.ch03.v38.factory.ConfigurableBeanFactory;
import com.hqd.ch03.v38.web.bind.suppor.DefaultSessionAttributeStore;
import com.hqd.ch03.v38.web.bind.suppor.SessionAttributeStore;
import com.hqd.ch03.v38.web.context.request.NativeWebRequest;
import com.hqd.ch03.v38.web.context.request.ServletWebRequest;
import com.hqd.ch03.v38.web.method.HandlerMethod;
import com.hqd.ch03.v38.web.method.annotation.ModelAndViewMethodReturnValueHandler;
import com.hqd.ch03.v38.web.method.annotation.ModelMethodProcessor;
import com.hqd.ch03.v38.web.method.annotation.RequestParamMethodArgumentResolver;
import com.hqd.ch03.v38.web.method.annotation.SessionAttributesHandler;
import com.hqd.ch03.v38.web.method.support.HandlerMethodArgumentResolver;
import com.hqd.ch03.v38.web.method.support.HandlerMethodArgumentResolverComposite;
import com.hqd.ch03.v38.web.method.support.HandlerMethodReturnValueHandler;
import com.hqd.ch03.v38.web.method.support.HandlerMethodReturnValueHandlerComposite;
import com.hqd.ch03.v38.web.servlet.ModelAndView;
import com.hqd.ch03.v38.web.servlet.mvc.method.AbstractHandlerMethodAdapter;
import com.hqd.ch03.v38.web.servlet.support.ModelAndViewContainer;
import com.hqd.ch03.v38.web.ui.ModelMap;
import com.hqd.ch03.v38.web.utils.WebUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
        implements BeanFactoryAware, InitializingBean {
    private final Map<Class<?>, SessionAttributesHandler> sessionAttributesHandlerCache = new ConcurrentHashMap<>(64);
    private boolean synchronizeOnSession = false;
    private BeanFactory beanFactory;
    private HandlerMethodArgumentResolverComposite argumentResolvers;
    private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
    private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
    private SessionAttributeStore sessionAttributeStore = new DefaultSessionAttributeStore();
    private int cacheSecondsForSessionAttributeHandlers = 0;

    protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
                                               HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        ModelAndViewContainer mavContainer = new ModelAndViewContainer();
        try {
            ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
            if (this.argumentResolvers != null) {
                invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
            }
            if (this.returnValueHandlers != null) {
                invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
            }
            invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
            invocableMethod.invokeAndHandle(webRequest, mavContainer);
            return getModelAndView(mavContainer, webRequest);
        } finally {
            webRequest.requestCompleted();
        }
    }

    private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
                                         NativeWebRequest webRequest) throws Exception {
        if (mavContainer.isRequestHandled()) {
            return null;
        }
        ModelMap model = mavContainer.getModel();
        return new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
    }

    protected ServletInvocableHandlerMethod createInvocableHandlerMethod(HandlerMethod handlerMethod) {
        return new ServletInvocableHandlerMethod(handlerMethod);
    }

    @Override
    protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response,
                                          HandlerMethod handlerMethod) throws Exception {
        ModelAndView mav;
        checkRequest(request);
        if (this.synchronizeOnSession) {
            HttpSession session = request.getSession(false);
            if (session != null) {
                Object mutex = WebUtils.getSessionMutex(session);
                synchronized (mutex) {
                    mav = invokeHandlerMethod(request, response, handlerMethod);
                }
            } else {
                mav = invokeHandlerMethod(request, response, handlerMethod);
            }
        } else {
            mav = invokeHandlerMethod(request, response, handlerMethod);
        }
        /**
         * 缓存控制
         */
        if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
            if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
                applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
            } else {
                prepareResponse(response);
            }
        }

        return mav;
    }

    private SessionAttributesHandler getSessionAttributesHandler(HandlerMethod handlerMethod) {
        return this.sessionAttributesHandlerCache.computeIfAbsent(
                handlerMethod.getBeanType(),
                type -> new SessionAttributesHandler(type, this.sessionAttributeStore));
    }

    @Override
    protected boolean supportsInternal(HandlerMethod handlerMethod) {
        return true;
    }

    private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
        List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);
        resolvers.add(new ServletRequestMethodArgumentResolver());
        resolvers.add(new ServletResponseMethodArgumentResolver());
        resolvers.add(new ModelMethodProcessor());
        resolvers.add(new MapMethodProcessor());
        resolvers.add(new RequestParamMethodArgumentResolver((ConfigurableBeanFactory) getBeanFactory(), true));
        return resolvers;
    }


    protected BeanFactory getBeanFactory() {
        return this.beanFactory;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        if (this.argumentResolvers == null) {
            List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
            this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
        }
        if (this.returnValueHandlers == null) {
            List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
            this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
        }
    }

    @Override
    @SuppressWarnings("deprecation")
    protected long getLastModifiedInternal(HttpServletRequest request, HandlerMethod handlerMethod) {
        return -1;
    }

    private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
        List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(20);
        handlers.add(new ModelAndViewMethodReturnValueHandler());
        handlers.add(new ModelMethodProcessor());
        handlers.add(new MapMethodProcessor());
        handlers.add(new ViewNameMethodReturnValueHandler());
        return handlers;
    }
}